package com.speedchina.oauth2_permission.aspect;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.speedchina.framework.exception.BException;
import com.speedchina.framework.exception.BaseSqlException;
import com.speedchina.oauth2_permission.properties.ThreadProperties;
import com.speedchina.oauth2_permission.thread.SysLogThreadFactory;
import com.speedchina.oauth2_permission.utils.SecurityUtils;
import com.speedchina.permission.base.util.UserAgentUtil;
import com.speedchina.permission.domain.sys.entity.LogOperation;
import com.speedchina.permission.domain.sys.entity.User;
import com.speedchina.permission.service.sys.LogOperationService;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author Winter
 */
@Aspect
@Order(5)
@Component
public class SysLogAspect {

    @Resource
    ThreadProperties threadProperties;
    private ThreadLocal<Date> startTime = new ThreadLocal<>();
    /**
     * 线程池 异步记录日志
     */
    private ExecutorService logExecutorService;
    @Autowired

    LogOperationService logOperationService;
    @Autowired
    private ObjectMapper objectMapper;

    @PostConstruct
    public void init(){
        logExecutorService = new ThreadPoolExecutor(threadProperties.getCorePoolSize(), threadProperties.getMaximumPoolSize(),
                threadProperties.getKeepAliveTime(), TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>(), new SysLogThreadFactory(threadProperties.getThreadName()));
    }
    @Pointcut("@annotation(com.speedchina.oauth2_permission.aspect.SysLog)")
    public void webLog() {
    }

    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint) {
        startTime.set(new Date());
    }

    @Around("webLog()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        try {
            return proceedingJoinPoint.proceed();
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
    }

    /**
     * 方法调用后触发 , 记录正常操作
     *
     * @param joinPoint
     * @throws ClassNotFoundException
     */
    @AfterReturning(returning = "ret", pointcut = "webLog()")
    public void doAfterReturning(JoinPoint joinPoint, Object ret) {
        try {
            // 接收到请求，记录请求内容
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            HttpSession session = (HttpSession) attributes.resolveReference(RequestAttributes.REFERENCE_SESSION);

            //获取传入目标方法的参数
            Object[] args = joinPoint.getArgs();
            for (int i = 0; i < args.length; i++) {
                Object o = args[i];
                if (o instanceof ServletRequest || (o instanceof ServletResponse) || o instanceof MultipartFile) {
                    args[i] = o.toString();
                }
            }

            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            SysLog mylog = method.getAnnotation(SysLog.class);
            String ip = attributes.getRequest().getRemoteAddr();
            String realIP = attributes.getRequest().getHeader("X-Real-IP");
            if (!StringUtils.isEmpty(realIP)){
                ip+=(","+realIP);
            }
            User user = SecurityUtils.getLoginUser();
            Map<String, String> map = UserAgentUtil.getInfo(request);
            String className = joinPoint.getTarget().getClass().getName();
            // 请求的方法参数名称
            LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
            String[] paramNames = u.getParameterNames(method);

            StringBuilder params = new StringBuilder();
            if (args != null && paramNames != null) {
                params = handleParams(params, args, Arrays.asList(paramNames));
            }
            LogOperation logOperation = new LogOperation(null, user.getUsername(), mylog.value(),
                    startTime.get(), className + "." + method.getName() + "()", params.toString(), ip, null,
                    new Date(), user.getId(), map.get("system"), map.get("browserName"), null, request.getRequestURI());
            /* logOperationService.save(logOperation); */
            logExecutorService.submit(() -> {
                logOperationService.save(logOperation);
            });
        } catch (Exception e) {
            System.err.println(e);
        } finally {

        }
    }

    /**
     * 发生异常，走此方法
     *
     * @param joinPoint
     * @param e
     */
    @AfterThrowing(pointcut = "webLog()", throwing = "e")
    public void AfterThrowing(JoinPoint joinPoint, Throwable e) {
//        try {
            // 接收到请求，记录请求内容
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            HttpSession session = (HttpSession) attributes.resolveReference(RequestAttributes.REFERENCE_SESSION);

            //获取传入目标方法的参数
            Object[] args = joinPoint.getArgs();
            for (int i = 0; i < args.length; i++) {
                Object o = args[i];
                if (o instanceof ServletRequest || (o instanceof ServletResponse) || o instanceof MultipartFile) {
                    args[i] = o.toString();
                }
            }

            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            SysLog mylog = method.getAnnotation(SysLog.class);
            String ip = attributes.getRequest().getRemoteAddr();
            String realIP = attributes.getRequest().getHeader("X-Real-IP");
            if (!StringUtils.isEmpty(realIP)){
                ip+=(","+realIP);
            }
            User user = SecurityUtils.getLoginUser();
            Map<String, String> map = UserAgentUtil.getInfo(request);
            String className = joinPoint.getTarget().getClass().getName();
            // 请求的方法参数名称
            LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
            String[] paramNames = u.getParameterNames(method);

            StringBuilder params = new StringBuilder();
            if (args != null && paramNames != null) {
                params = handleParams(params, args, Arrays.asList(paramNames));
            }
            String msg = null;
            if(e instanceof BaseSqlException) {
                msg = e.getMessage();
            } else if(e instanceof BException) {
                msg = ((BException) e).getMsg();
            } else{
                msg = e.getMessage() == null ? (e.getCause()!=null?e.getCause().toString():null ): e.getLocalizedMessage();
            }
            LogOperation logOperation = new LogOperation(null, user.getUsername(), mylog.value(),
                    startTime.get(), className + "." + method.getName() + "()", params.toString(), ip, null,
                    new Date(), user.getId(), map.get("system"), map.get("browserName"), msg, request.getRequestURI());
            /* logOperationService.save(logOperation); */
            logExecutorService.submit(() -> {
                logOperationService.save(logOperation);
            });
//        } catch (Exception ex) {
//            System.err.println(ex);
//        } finally {
//
//        }
    }

    @SuppressWarnings("all")
    private StringBuilder handleParams(StringBuilder params, Object[] args, List paramNames) {
        try {
            for (int i = 0; i < args.length; i++) {
                if (args[i] instanceof Map) {
                    Set set = ((Map) args[i]).keySet();
                    List<Object> list = new ArrayList<>();
                    List<Object> paramList = new ArrayList<>();
                    for (Object key : set) {
                        list.add(((Map) args[i]).get(key));
                        paramList.add(key);
                    }
                    return handleParams(params, list.toArray(), paramList);
                } else {
                    if (args[i] instanceof Serializable) {
                        Class<?> aClass = args[i].getClass();
                        try {
                            aClass.getDeclaredMethod("toString", new Class[]{null});
                            // 如果不抛出 NoSuchMethodException 异常则存在 toString 方法 ，安全的 writeValueAsString ，否则 走 Object的 toString方法
                            params.append(" ").append(paramNames.get(i)).append(": ").append(objectMapper.writeValueAsString(args[i]));
                        } catch (NoSuchMethodException e) {
                            params.append(" ").append(paramNames.get(i)).append(": ").append(objectMapper.writeValueAsString(args[i].toString()));
                        }
                    } else if (args[i] instanceof MultipartFile) {
                        MultipartFile file = (MultipartFile) args[i];
                        params.append(" ").append(paramNames.get(i)).append(": ").append(file.getName());
                    } else {
                        params.append(" ").append(paramNames.get(i)).append(": ").append(args[i]);
                    }
                }
            }
        } catch (Exception ignore) {
            params.append("参数解析失败");
        }
        return params;
    }
}
